<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Top Contributors</title>
<link href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/bootstrap/4.6.1/css/bootstrap.css" type="text/css" rel="stylesheet" />
<link rel="stylesheet" href="./index.css">

<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.7.1.min.js"  ></script>
<script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/bootstrap/4.6.1/js/bootstrap.bundle.min.js" type="application/javascript"></script>
<script src="https://code.highcharts.com/highcharts.js"></script>
</head>
<body>

<figure class="highcharts-figure">
    <div id="parent-container">
		<div id="play-controls">
			<button id="play-pause-button" class="fa fa-play" title="play"></button>
			<input id="play-range" type="range"  min="0" max="10" value="0" />
		</div>
		<div id="container"></div>
	</div>
</figure>
<!-- Modal -->
<div class="modal fade" id="contributorModal" tabindex="-1" aria-labelledby="contributorModalLabel" aria-hidden="true">
  <div class="modal-dialog modal-xl">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
       <div class="container-fluid">
      <div class="row">
		<div class="col-xl-3 text-center">
				<img class="avatar"/>
				<h3 class="contributorName"></h3>
				<button class="contributorEmail  btn btn-info" type="button" data-toggle="collapse" data-target="#collapseEmails"></button>
				<div class="collapse" id="collapseEmails">
				</div>
		</div>
        <div class="col-xl-9">
        <div id="contributorChartContainer" ></div>
        </div>
      </div>
      <div class="row" >
      <div class="col-xl-12">
      	<table class="table" id="commitsTable">
      	<caption>Commits</caption>
  <thead>
    <tr>
      <th scope="col">#</th>
      <th scope="col">Time</th>
      <th scope="col">Message</th>
    </tr>
  </thead>
  <tbody>
    <tr id="commitRowTemplate">
      <th scope="row" class="commitNo">1</th>
      <td class="commitTime"></td>
      <td >
      	<a href="" target="_blank" class="commitUrl"><span class="commitMessage"  ></span></a>
      </td>
    </tr>
    <tr id="loadMoreRow">
	  <td colspan="4">
	    <div class="d-flex justify-content-center">
	     <button class="btn btn-primary"   id="loadMoreButton">
	     <i class="fa fa-angle-double-down fa-fw" aria-hidden="true"></i>Load More
	     </button>
	    </div>
	  </td>
	</tr>
    <tr id="loadingRow">
	  <td colspan="4">
	    <div class="d-flex justify-content-center">
	     <i class="fa fa-spinner fa-pulse fa-fw fa-2x"></i>Loading...
	    </div>
	  </td>
	</tr>
  </tbody>
</table>
		</div>
		</div>
      </div>
      </div>
      </div>
    </div>
  </div>
</div>
<script type="text/javascript">
/*
* Animate dataLabels functionality
*/
(function (H) {
const FLOAT = /^-?\d+\.?\d*$/;

// Add animated textSetter, just like fill/strokeSetters
H.Fx.prototype.textSetter = function () {
    let startValue = this.start.replace(/ /g, ''),
        endValue = this.end.replace(/ /g, ''),
        currentValue = this.end.replace(/ /g, '');

    if ((startValue || '').match(FLOAT)) {
        startValue = parseInt(startValue, 10);
        endValue = parseInt(endValue, 10);

        // No support for float
        currentValue = Highcharts.numberFormat(
            Math.round(startValue + (endValue - startValue) * this.pos),
            0
        );
    }

    this.elem.endText = this.end;

    this.elem.attr(this.prop, currentValue, null, true);
};

// Add textGetter, not supported at all at this moment:
H.SVGElement.prototype.textGetter = function () {
    const ct = this.text.element.textContent || '';
    return this.endText ? this.endText : ct.substring(0, ct.length / 2);
};

// Temporary change label.attr() with label.animate():
// In core it's simple change attr(...) => animate(...) for text prop
H.wrap(H.Series.prototype, 'drawDataLabels', function (proceed) {
    const attr = H.SVGElement.prototype.attr,
        chart = this.chart;

    if (chart.sequenceTimer) {
        this.points.forEach(point =>
            (point.dataLabels || []).forEach(
                label =>
                    (label.attr = function (hash) {
                        if (
                            hash &&
                            hash.text !== undefined &&
                            chart.isResizing === 0
                        ) {
                            const text = hash.text;

                            delete hash.text;

                            return this
                                .attr(hash)
                                .animate({ text });
                        }
                        return attr.apply(this, arguments);

                    })
            )
        );
    }

    const ret = proceed.apply(
        this,
        Array.prototype.slice.call(arguments, 1)
    );

    this.points.forEach(p =>
        (p.dataLabels || []).forEach(d => (d.attr = attr))
    );

    return ret;
});
}(Highcharts));
const contributorAvatars = {
		  "Christoph Hellwig" : {
			    "avatar" : "https://training.linuxfoundation.org/wp-content/uploads/2018/06/christoph_hellwig.png"
			  },
			  "Mauro Carvalho Chehab" : {
			    "avatar" : "https://avatars.githubusercontent.com/u/6566927"
			  },
			  "Takashi Iwai" : {
				"github": "https://github.com/tiwai",
			    "avatar" : "https://lac.linuxaudio.org/2003/zkm/photos/takashi_iwai.jpg"
			  },
			  "Krzysztof Kozlowski" : {
			    "avatar" : "https://avatars.githubusercontent.com/u/10425763"
			  },
			  "Al Viro" : {
			    "avatar" : "https://static.lwn.net/images/2016/lsf-viro-sm.jpg"
			  },
			  "Arnd Bergmann" : {
			    "avatar" : "https://avatars.githubusercontent.com/u/1080275"
			  },
			  "Chris Wilson" : {
			    "avatar" : "https://avatars.githubusercontent.com/u/958432"
			  },
			  "Thomas Gleixner" : {
			    "avatar" : "https://www.linuxidc.com/upload/2015_01/150105151310002.jpg"
			  },
			  "Geert Uytterhoeven" : {
			    "avatar" : "https://avatars.githubusercontent.com/u/4139050"
			  },
			  "Andy Shevchenko" : {
			    "avatar" : "https://avatars.githubusercontent.com/u/78496"
			  },
			  "Johannes Berg" : {
				"avatar" : "http://www.linuxstory.org/wp-content/uploads/2015/04/Johannes_Berg.jpg"
//			    "avatar" : "https://avatars.githubusercontent.com/u/96500427"
			  },
			  "Mark Brown" : {
			    "avatar" : "https://avatars.githubusercontent.com/u/2903"
			  },
			  "Ville Syrjälä" : {
			    "avatar" : "https://avatars.githubusercontent.com/u/1081303"
			  },
			  "Dan Carpenter" : {
			    "avatar" : "https://avatars.githubusercontent.com/u/10603193"
			  },
			  "H Hartley Sweeten" : {
			    "avatar" : "https://avatars.githubusercontent.com/u/1649185"
			  },
			  "Uwe Kleine-König" : {
			    "avatar" : "https://avatars.githubusercontent.com/u/5037590"
			  },
			  "Eric Dumazet" : {
			    "avatar" : "https://image-uniservice.linternaute.com/image/450/3139063120/59359.jpg"
			  },
			  "Colin Ian King" : {
			    "avatar" : "https://avatars.githubusercontent.com/u/9316527"
			  },
			  "Kuninori Morimoto" : {
			    "avatar" : "https://avatars.githubusercontent.com/u/154697"
			  },
			  "Alex Deucher" : {
			    "avatar" : "https://avatars.githubusercontent.com/u/118310711"
			  },
			  "Hans Verkuil" : {
			    "avatar" : "https://www.linuxfoundation.org/hubfs/hverkuil.jpg"
			  },
			  "Linus Torvalds" : {
				  "avatar" : "https://avatars.githubusercontent.com/u/1024025"
			  },
			  "David S. Miller" : {
				  "avatar" : "https://avatars.githubusercontent.com/u/1053866"
			  },
			  "Greg Kroah-Hartman" : {
				  "avatar" : "https://avatars.githubusercontent.com/u/14953"
			  }
			};
const startYear = #{startYear},
endYear = #{endYear},
orderBy = "#{orderBy}",
period = "#{period}",
access_token = "#{giteeToken}",
btn = document.getElementById('play-pause-button'),
input = document.getElementById('play-range'),
years = document.getElementById('years'),
nbr = 20;
let dataset = #{contributorsJson};
let chart;
let timeCategories = [];
let yearCategories = [];
let playInterval = 2000;
let startTimeCategory  ;
for (let i = startYear; i <= endYear; i++) {
	yearCategories.push(""+i);
	if(period == "monthly"){
		for(let m=1; m<=12; m++){
			const timeCategory = i+"-"+(m>=10?m:("0"+m));
			timeCategories.push(timeCategory);
			
		}
	}else if(period == "dayly"){
		playInterval = 100;
		let daysInYear = 365;
		if(i%400==0 || (i%4==0 && i%100!=0)){
			daysInYear = 366;
		}
		let  firstDayInYear = new Date(i,0,1);
		for(let d=0; d<daysInYear; d++){
			const day = new Date(firstDayInYear.getTime()+d*24*60*60*1000);
			const month = day.getMonth()+1;
			const year = day.getFullYear();
			const date = day.getDate();
			const timeCategory =year+"-"+(month>=10?month:("0"+month))+"-"+(date>=10?date:("0"+date));
			timeCategories.push(timeCategory);
		}
	}else{
		timeCategories.push(""+i);
	}
}
input.setAttribute('max', timeCategories.length-1);




function getData(time) {
	let oldData;
	if(chart && chart.series[0]){
		oldData = chart.series[0].data;
	}
	const output = Object.entries(dataset)
	    .map(contributor => {
	        const [contributorName, contributorData] = contributor;
	        let color ;
	        if(oldData){
				const oldPoint  = oldData.find(p=>p.name==contributorName);
				if(oldPoint){
					color = oldPoint.color;
				}
			}
	        return {
	            name: contributorName, 
	            y: Number(!!contributorData[time]?contributorData[time]:0)
	            };
	    })
	    .sort((a, b) => b.y - a.y);
	return [time, output.length>nbr?output.slice(0, nbr):output.concat(new Array(nbr-output.length).fill({name:""}))];
}
function showContributorChart(contributorName){
	const contributorData = dataset[contributorName];
	$("#contributorModalLabel").html(contributorName+"&nbsp;<span style='font-size: 0.8em'>"+contributorData["email"]+"</span>");
	console.log(contributorName, contributorData);
	const yearlyCommits =contributorData["yearlyCommits"];
	Highcharts.chart('contributorChartContainer', {
			chart: {
				height: 300
			},
			title: {
				text:null
			},
		  xAxis: {
		        type: 'category',
		        categories: yearCategories
		  },
		  legend: {
			  enabled: false
		  },
		  yAxis: [{
			  title: {
			      text: 'Commits'
			  }
		  },{
			 opposite: true,
			 title: {
			      text: 'Lines'
			  }
		  }],
		  credits: {
			  enabled: false
		  },
		series: [{
			type: "column",
			name: "Commits",
			yAxis: 0,
			data: yearCategories.map(year=>{
				let commitsCount =0;
				if(!yearlyCommits){
					commitsCount = 0;
				}else{
					const yearCommits = yearlyCommits.find(item=>item.year==year);
					if(!!yearCommits && yearCommits.commits){
						commitsCount = yearCommits.commits;
					}
				}
				return [year, commitsCount];
			})
			
		},{
			type: "line",
			name: "Lines",
			color: "#ff6c0e",
			yAxis: 1,
			data: yearCategories.map(year=>{
				let lines =0;
				if(!!yearlyCommits){
					const yearCommits = yearlyCommits.find(item=>item.year==year);
					if(!!yearCommits && yearCommits.lines){
						lines = yearCommits.lines;
					}
				}
				return [year, lines];
			})
			
		}]
		
	},function(){
		let avatar ="https://www.kernel.org/theme/images/logos/tux.png";
		if(!!contributorAvatars[contributorName] && !!contributorAvatars[contributorName]["avatar"]){
			avatar = contributorAvatars[contributorName]["avatar"];
		}	
		
		$("#contributorModal .avatar").attr("src",avatar);
		$("#contributorModal .contributorName").text(contributorName);
		$("#contributorModal .contributorEmail").text(contributorData["email"]);
		
		$("#commitsTable .commitRow").remove();
		
		loadCommits(contributorName, 1);
		if(contributorData["emails"]){
			let emailsHtml = "<ul>";
			for(const timeEmail of contributorData["emails"]){
				emailsHtml += `<li>${timeEmail.time}: ${timeEmail.email}</li>`;
			}
			emailsHtml+="</ul>";
			$("#collapseEmails").html(emailsHtml);
		}
		
		$("#contributorModal").modal('show');
	});
}
function loadCommits(contributorName, page){
	$("#loadingRow").show();
	$("#loadMoreRow").hide().off("click");
	let pageSize = 50;
	$.getJSON("https://gitee.com/api/v5/repos/mirrors/linux_old1/commits",{
		sha:"master",
		author:contributorName,
		since:startYear+"-01-01",
		until:endYear+"-12-31",
		page:page,
		per_page:pageSize,
		access_token:access_token
	},function(data){
		console.log(data);
		$("#loadingRow").hide();
		data.forEach((commit, idx)=>{
			let $commitRow =$("#commitRowTemplate").clone(true).removeAttr("id").addClass("commitRow");
			$commitRow.find(".commitNo").text(pageSize*(page-1)+idx+1);
			$commitRow.find(".commitTime").html(new Date(commit["commit"]["committer"]["date"]).toISOString().substr(0,19).replace("T","<br>"));
			const commitMessage = commit["commit"]["message"];
			const commitMessageLines = commitMessage.split("\n");
			$commitRow.find(".commitMessage")
				.text(commitMessageLines[0])
				.tooltip({
					html: true,
					placement:"right",
					"title":"<h5>"+commitMessageLines[0]+"</h5>"+commitMessageLines.slice(1).join("<br>")
					});
			$commitRow.find(".commitUrl").attr("href",commit["html_url"]);
			$("#commitRowTemplate").before($commitRow);
		});	
		if(data.length==pageSize){
			$("#loadMoreRow").show();
			$("#loadMoreRow").one("click",function(){
				loadCommits(contributorName, page+1);
			});
		}else{
			$("#loadMoreRow").hide();
		}	

});
}
function getSubtitle() {
	const time = timeCategories[parseInt(input.value, 10)];
	return `<span style="font-size: 30px;color: #333;">${time}</span>`;
}

(async () => {

Highcharts.AST.allowedReferences.push('javascript:');

chart = Highcharts.chart('container', {
    chart: {
		marginLeft: 200,
        animation: {
            duration: Math.min(playInterval, 500)-10
        }
    },
    title: {
        text: 'Top Linux Contributors from #{startYear} to #{endYear}. <span style="font-size: 0.8em;color: #999">by #{orderBy}</span>',
        align: 'left'
    },
    subtitle: {
        useHTML: true,
        text: getSubtitle(),
        floating: true,
        align: 'right',
        verticalAlign: 'top',
        y: 5,
        x: -20
    },
	credits:{
		enabled: false
		},
    legend: {
        enabled: false
    },
    xAxis: {
        type: 'category',
        labels: {
	        formatter: function () {
	            return "<a href='javascript:showContributorChart(\""+this.value+"\")' style='font-size: 1.2em'><b>"+this.value+"</b></a><br>"
	            +"<span style='font-size: 0.8em'>"+dataset[this.value]["email"]+"</span>";
	        }
        }
    },
    yAxis: {
        opposite: true,
        tickPixelInterval: 100,
        title: {
            text: null
        }
    },
    plotOptions: {
        series: {
            animation: false,
            groupPadding: 0,
            pointPadding: 0.1,
            borderWidth: 0,
            colorByPoint: true,
            dataSorting: {
                enabled: true,
                matchByName: true
            },
            type: 'bar',
            dataLabels: {
                enabled: true,
                align: 'right',
                x:-10,
                style:{
					fontSize: '1.0em'
				}
            }
        }
    },
    series: [
        {
            type: 'bar',
            name: timeCategories[0],
            data: getData(timeCategories[0])[1]
        }
    ]
});
})();

/*
* Pause the timeline, either when the range is ended, or when clicking the
* pause button. Pausing stops the timer and resets the button to play mode.
*/
function pause(button) {
	button.title = 'play';
	button.className = 'fa fa-play';
	clearTimeout(chart.sequenceTimer);
	chart.sequenceTimer = undefined;
}

/*
* Update the chart. This happens either on updating (moving) the range input,
* or from a timer when the timeline is playing.
*/
function update(increment) {
    if (increment) {
        input.value = parseInt(input.value, 10) + increment;
    }
    if (input.value >= timeCategories.length - 1) {
        // Auto-pause
        pause(btn);
    }

    chart.update(
        {
            subtitle: {
                text: getSubtitle()
            }
        },
        false,
        false,
        false
    );
	const time = timeCategories[parseInt(input.value, 10)];
    chart.series[0].update({
        name: time,
        data: getData(time)[1]
    });
}

/*
 * Play the timeline.
 */
function play(button) {
    button.title = 'pause';
    button.className = 'fa fa-pause';
    chart.sequenceTimer = setInterval(function () {
        update(1);
    }, playInterval);
}

btn.addEventListener('click', function () {
    if (chart.sequenceTimer) {
        pause(this);
    } else {
        play(this);
    }
});
/*
 * Trigger the update on the range bar click.
 */
input.addEventListener('change', function () {
    update();
});

</script>
</body>
</html>